#include <mcwidget.h>
#include <opengl/application.h>
#include <ctime>

namespace mc
{
MCWidget::MCWidget(int widget, int height, const std::string& filename) : 
    opengl::Window(widget, height, "My MineCraft", glm::vec4(0.0f, 0.0f, 0.0f, 0.0f)),
    _world(nullptr),
    _loader(nullptr),
    _player(nullptr),
    _deltaTime(0.0f), _lastFrame(0.0f), _lastX(widget / 2), _lastY(height / 2), _firstMouse(true)
{
    this->initGame(filename);
}

MCWidget::~MCWidget() noexcept
{
#ifdef DEBUG
    std::cout << "save world\n";
#endif
    this->_loader->save(this->_player->footPosition() + glm::vec3(0.5f, 0.0, 0.5f));

#ifdef DEBUG
    std::cout << "delete mc widget\n";
#endif
    this->free();
}

//! @brief 初始化资源
void MCWidget::initGame(const std::string& filename)
{
    srand(time(NULL));
    // 创建世界
#ifdef DEBUG
    std::cout << "create world\n";
#endif
    this->_world = util::Singleton<World>::instance();

    // 初始化世界资源，传入 projection 矩阵
    this->_world->initial(glm::perspective(glm::radians(45.0f), static_cast<float>(this->_width) / static_cast<float>(this->_height), 0.1f, 100.0f));

#ifdef DEBUG
    std::cout << "active textures\n";
#endif
    // 激活纹理
    opengl::Application::instance()->activeTextures();

#ifdef DEBUG
    std::cout << "loader world\n";
#endif
    // 加载世界
    this->_loader = Loader::instance(filename);
    glm::vec3 playerPos = this->_loader->load();

#ifdef DEBUG
    std::cout << "create player\n";
#endif
    // 创建玩家
    this->_player = new Player(playerPos);
}

//! @brief 释放资源
void MCWidget::freeResources()
{
    // 释放世界
    util::Singleton<World>::free();
    Loader::free();

    // 释放玩家
    delete this->_player;
}

//! @brief 运行帧计算
void MCWidget::calFrameTime()
{
    float currentFrame = glfwGetTime();
    this->_deltaTime = currentFrame - this->_lastFrame;
    this->_lastFrame = currentFrame;
}

//! @brief 渲染函数
void MCWidget::render() 
{
    this->_player->update(this->_deltaTime);
    this->_world->draw(this->_player->viewMat());
}

//! @brief 程序输入控制
void MCWidget::processInput() 
{
    this->calFrameTime();

    if (glfwGetKey(this->_window, GLFW_KEY_W) == GLFW_PRESS)
        this->_player->move(opengl::CameraMovement::FORWARD, this->_deltaTime);
    else if (glfwGetKey(this->_window, GLFW_KEY_S) == GLFW_PRESS)
        this->_player->move(opengl::CameraMovement::BACKWARD, this->_deltaTime);
    
    if (glfwGetKey(this->_window, GLFW_KEY_A) == GLFW_PRESS)
        this->_player->move(opengl::CameraMovement::LEFT, this->_deltaTime);
    else if (glfwGetKey(this->_window, GLFW_KEY_D) == GLFW_PRESS)
        this->_player->move(opengl::CameraMovement::RIGHT, this->_deltaTime);
    
    if (glfwGetKey(this->_window, GLFW_KEY_SPACE) == GLFW_PRESS)
        this->_player->jump(this->_deltaTime);
    else if (glfwGetKey(this->_window, GLFW_KEY_X) == GLFW_PRESS)
        this->_player->sneak(this->_deltaTime);
}

//! @brief 更新视口大小回调函数
void MCWidget::framebufferSizeCallback(GLFWwindow* window, int width, int height) 
{
    glViewport(0, 0, width, height);
}

//! @brief 鼠标移动回调
void MCWidget::cursorPosCallback(GLFWwindow* window, double xpos, double ypos) 
{
    if (this->_firstMouse)
    {
        this->_lastX = xpos;
        this->_lastY = ypos;
        this->_firstMouse = false;
    }

    // 计算位置差
    float xoffset = xpos - this->_lastX;
    float yoffset = this->_lastY - ypos;
    this->_lastX = xpos;
    this->_lastY = ypos;

    this->_player->changeSight(xoffset, yoffset);
}

//! @brief 鼠标滚轮回调
void MCWidget::scrollCallback(GLFWwindow* window, double xoffset, double yoffset) 
{
    this->_player->changeHodingBlock(static_cast<char>(yoffset));
}

//! @brief 鼠标点击回调
void MCWidget::mouseButtonCallback(GLFWwindow *window, int button, int action, int mods) 
{
	if (action == GLFW_PRESS)
	{
		switch (button)
		{
		case GLFW_MOUSE_BUTTON_RIGHT:
            this->_player->placeBlock();
			break;
		case GLFW_MOUSE_BUTTON_LEFT:
            this->_player->removeBlock();
			break;
		}
	}
}

//! @brief 键盘输入回调
void MCWidget::keyboardCallback(GLFWwindow* window, int key, int scancode, int action, int mods) 
{
    if (action == GLFW_PRESS)
    {
        switch (key)
        {
        case GLFW_KEY_ESCAPE:
            glfwSetWindowShouldClose(this->_window, true);
            break;
        case GLFW_KEY_Q:
            this->_player->switchMovingWay();
            break;
        case GLFW_KEY_LEFT_ALT:
            this->_player->setSneaking(true);
            break;
        default:
            break;
        }
    }
    else
    {
        switch (key)
        {
        case GLFW_KEY_LEFT_ALT:
            this->_player->setSneaking(false);
        default:
            break;
        }
    }
}

}